package com.example.wexcle.demos.web.service.impl;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.example.wexcle.demos.web.config.ListUtils;
import com.example.wexcle.demos.web.entity.File;
import com.example.wexcle.demos.web.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Service
public class EasyExcelImportHandler extends AnalysisEventListener<File>   {
    /*成功数据*/
//    private final CopyOnWriteArrayList<File> successList = new CopyOnWriteArrayList<>();
    /**
     * 1)将原来的线程安全的CopyOnWriteArrayList换为ArrayList，前者虽然可保线程安全，但存储数据性能很差；
     * 2）将原来单批次2000调整为100000，这个参数是因电脑而异的，并没有最佳数值。
     */
    private final List<File> successList =  new ArrayList<>();

    /*单次处理条数*/
    private final static int BATCH_COUNT = 100000;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private UserMapper userMapper;
    @Override
    public void invoke(File file, AnalysisContext analysisContext) {
        if(StringUtils.isNotBlank(file.getFilename())){
            successList.add(file);
            return;
        }
        if(successList.size() >= BATCH_COUNT){
            System.out.println("读取数据:"+ successList.size());
            saveData();
            //清理集合便于GC回收
            successList.clear();
        }

    }

    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    /**
     * 优化方法1：采用多线程读取数据
     */
    private void saveData() {
        List<List<File>> lists = ListUtils.partition(successList,1000);


        /**
         * CountDownLatch是一个同步工具类，用来协调多个线程之间的同步，或者说起到线程之间的通信（而不是用作互斥的作用）。
         *
         * CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后，再继续执行。
         * 使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后，计数器的值就会减一。
         * 当计数器的值为0时，表示所有的线程都已经完成一些任务，然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
         *  CountDownLatch典型用法：1、某一线程在开始运行前等待n个线程执行完毕。
         *  将CountDownLatch的计数器初始化为new CountDownLatch(n)，每当一个任务线程执行完毕，
         *  就将计数器减1 countdownLatch.countDown()，当计数器的值变为0时，在CountDownLatch上await()的线程就会被唤醒。
         *  一个典型应用场景就是启动一个服务时，主线程需要等待多个组件加载完毕，之后再继续执行。
         */
        CountDownLatch countDownLatch = new CountDownLatch(lists.size());
        for (List<File> list : lists) {
            threadPoolExecutor.execute(()->{//这里开始的代码块就是run()方法里线程要执行的任务

                //这里相当于threadPoolExecutor.execute()对Runnable接口的实现重写run方法
                //其中包含多个步骤
                //1.创建一个类实现Runnable接口，假如这个类是WzhRunnable,则
                /**
                 * public class WzhRunnable implements Runnable{
                 *    @Override
                 *     public void run(){
                 *         ................此处的代码块就是{}括号里的代码
                 *     }
                 * }
                 * threadPoolExecutor.execute(new WzhRunnable);
                 */

                try {
                    userMapper.insertSelective(list.stream().map(o -> {
                        File file = new File();
                        file.setId(o.getId());
                        file.setFilename(o.getFilename());
                        file.setCount(o.getCount());
                        file.setLocaladdress(o.getLocaladdress());
                        file.setNumber(o.getNumber());
                        file.setState(o.getState());
                        return file;
                    }).collect(Collectors.toList()));
                } catch (Exception e) {
                    System.out.println("启动线程失败:"+e.getMessage()+ e);
                } finally {
                    //执行完一个线程减1,直到执行完
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有线程执行完
        try {
            countDownLatch.await();//阻塞当前线程，直到计数器的值为0
        } catch (Exception e) {
            System.out.println("等待所有线程执行完异常:"+ e.getMessage()+e);
        }
        // 提前将不再使用的集合清空，释放资源
        successList.clear();
        lists.clear();
    }

    /**
     * 所有数据读取完成之后调用
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //读取剩余数据
        if(CollectionUtils.isNotEmpty(successList)){
            System.out.println("读取数据："+successList.size());
            saveData();
        }
    }

}
